# encoding=utf-8
# MVVM架构中的view视图，App框架
# BingoLee 2024-04-16
import os
import tkinter as tk
import customtkinter as ctk
from CTkMessagebox import CTkMessagebox
from PIL import Image

from utils.kit_profile import Profile
from viewmodel.kit_viewmodel import Topic, TopicBinder
from view.view_modules import TextBox, Markdownx
from view.at_cmd_page import AtCmdPage
from view.stress_test_page import StressTestPage
from view.log_offline_page import LogOfflinePage
from view.log_online_page import LogOnlinePage
from view.bin_editor_page import BinEditorPage
from view.map_analyze_page import MapAnalyzePage
from view.settings_page import SettingsPage


class App(ctk.CTk):

    def __init__(self):
        ctk.CTk.__init__(self)
        # 基础窗体设置
        self.title(f"{Profile.app_name} v{Profile.app_version}")
        width, height, min_w, min_h = self.winfo_screenwidth(), self.winfo_screenheight(), 1366, 768
        self.geometry(f"{min_w}x{min_h}+{max(0, int((width - min_w) / 2))}+{max(0, int((height - min_h) / 2))}")
        self.update()
        self.minsize(min_w, min_h)
        self.maxsize(width, height)
        self.protocol("WM_DELETE_WINDOW", self.close)
        # 加载图标
        if os.name == 'nt':
            self.iconbitmap(os.path.join(Profile.image_dir, 'app.ico'))
        else:
            self.iconphoto(True, tk.PhotoImage(file=os.path.join(Profile.image_dir, 'app.png')))
        self.app_image = ctk.CTkImage(Image.open(os.path.join(Profile.image_dir, 'app.png')), size=(34, 34))
        self.coming_img = ctk.CTkImage(Image.open(os.path.join(Profile.image_dir, 'coming.png')), size=(400, 200))
        self.at_cmd_img = ctk.CTkImage(
            light_image=Image.open(os.path.join(Profile.image_dir, "at_cmd-0.png")),
            dark_image=Image.open(os.path.join(Profile.image_dir, "at_cmd-1.png")), size=(22, 22))
        self.stress_test_img = ctk.CTkImage(
            light_image=Image.open(os.path.join(Profile.image_dir, "stress_test-0.png")),
            dark_image=Image.open(os.path.join(Profile.image_dir, "stress_test-1.png")), size=(22, 22))
        self.log_offline_img = ctk.CTkImage(
            light_image=Image.open(os.path.join(Profile.image_dir, "log_offline-0.png")),
            dark_image=Image.open(os.path.join(Profile.image_dir, "log_offline-1.png")), size=(22, 22))
        self.log_online_img = ctk.CTkImage(
            light_image=Image.open(os.path.join(Profile.image_dir, "log_online-0.png")),
            dark_image=Image.open(os.path.join(Profile.image_dir, "log_online-1.png")), size=(22, 22))
        self.bin_editor_img = ctk.CTkImage(
            light_image=Image.open(os.path.join(Profile.image_dir, "bin_editor-0.png")),
            dark_image=Image.open(os.path.join(Profile.image_dir, "bin_editor-1.png")), size=(22, 22))
        self.map_analyze_img = ctk.CTkImage(
            light_image=Image.open(os.path.join(Profile.image_dir, "map_analyze-0.png")),
            dark_image=Image.open(os.path.join(Profile.image_dir, "map_analyze-1.png")), size=(22, 22))
        self.settings_img = ctk.CTkImage(
            light_image=Image.open(os.path.join(Profile.image_dir, 'settings-0.png')),
            dark_image=Image.open(os.path.join(Profile.image_dir, 'settings-1.png')), size=(22, 22))
        # 初始化各界面类
        self.settings_page = SettingsPage(self)
        self.at_cmd_page = AtCmdPage(self)
        self.stress_test_page = StressTestPage(self)
        self.log_offline_page = LogOfflinePage(self)
        self.log_online_page = LogOnlinePage(self)
        self.bin_editor_page = BinEditorPage(self)
        self.map_analyze_page = MapAnalyzePage(self)
        self.at_cmd_button = None
        self.at_cmd_frame = None
        self.stress_test_button = None
        self.stress_test_frame = None
        self.log_offline_button = None
        self.log_offline_frame = None
        self.log_online_button = None
        self.log_online_frame = None
        self.bin_editor_button = None
        self.bin_editor_frame = None
        self.map_analyze_button = None
        self.map_analyze_frame = None
        self.settings_frame = None
        self.current_page = None
        self.show()

    def close(self):
        if CTkMessagebox(
            title="Exit?", message="确认退出吗？", icon="question", option_1="No", option_2="Yes"
        ).get() == 'Yes':
            self.quit()

    def show(self):
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=1)
        self.show_sidebar()
        self.update()
        # 布局各界面，但不显示
        self.at_cmd_frame = ctk.CTkFrame(self, fg_color="transparent")
        self.stress_test_frame = ctk.CTkFrame(self, fg_color="transparent")
        self.log_offline_frame = ctk.CTkFrame(self, fg_color="transparent")
        self.log_online_frame = ctk.CTkFrame(self, fg_color="transparent")
        self.bin_editor_frame = ctk.CTkFrame(self, fg_color="transparent")
        self.map_analyze_frame = ctk.CTkFrame(self, fg_color="transparent")
        self.settings_frame = ctk.CTkFrame(self, fg_color="transparent")
        self.at_cmd_page.pack(self.at_cmd_frame)
        self.stress_test_page.pack(self.stress_test_frame)
        self.log_offline_page.pack(self.log_offline_frame)
        self.log_online_page.pack(self.log_online_frame)
        self.bin_editor_page.pack(self.bin_editor_frame)
        self.map_analyze_page.pack(self.map_analyze_frame)
        self.settings_page.pack(self.settings_frame)
        # 显示AT页
        self.change_theme_event('light')
        # TODO: v0.0.1 to page 3
        self.select_page(3)

    def show_sidebar(self):
        """ 侧边栏 """
        sidebar_frame = ctk.CTkFrame(self, width=200, corner_radius=0)
        sidebar_frame.grid(row=0, column=0, rowspan=8, sticky="nsew")
        frame_style = {
            'master': sidebar_frame, 'corner_radius': 0, 'height': 50, 'border_spacing': 20,
            'font': ctk.CTkFont(size=18),  'fg_color': "transparent", 'text_color': ("gray10", "gray90"),
            'hover_color': ("gray70", "gray30"), 'anchor': "w"
        }
        self.at_cmd_button = ctk.CTkButton(
            text="  AT收发", image=self.at_cmd_img, command=lambda: self.select_page(0), **frame_style)
        self.stress_test_button = ctk.CTkButton(
            text="  压力测试", image=self.stress_test_img, command=lambda: self.select_page(1), **frame_style)
        self.log_offline_button = ctk.CTkButton(
            text="  离线日志", image=self.log_offline_img, command=lambda: self.select_page(2), **frame_style)
        self.log_online_button = ctk.CTkButton(
            text="  在线日志", image=self.log_online_img, command=lambda: self.select_page(3), **frame_style)
        self.bin_editor_button = ctk.CTkButton(
            text="  Bin修改", image=self.bin_editor_img, command=lambda: self.select_page(4), **frame_style)
        self.map_analyze_button = ctk.CTkButton(
            text="  Map分析", image=self.map_analyze_img, command=lambda: self.select_page(5), **frame_style)
        ctk.CTkLabel(
            sidebar_frame, text=f" {Profile.app_name}", image=self.app_image, compound="left",
            font=ctk.CTkFont(size=20, weight="bold")
        ).grid(row=0, column=0, padx=20, pady=20)
        self.at_cmd_button.grid(row=1, column=0, sticky="ew")
        self.stress_test_button.grid(row=2, column=0, sticky="ew")
        self.log_offline_button.grid(row=3, column=0, sticky="ew")
        self.log_online_button.grid(row=4, column=0, sticky="ew")
        self.bin_editor_button.grid(row=5, column=0, sticky="ew")
        self.map_analyze_button.grid(row=6, column=0, sticky="ew")

        settings_frame = ctk.CTkFrame(sidebar_frame, fg_color="transparent", height=30)
        settings_frame.grid(row=7, column=0, pady=50, sticky="sew")
        theme_box = ctk.CTkComboBox(
            settings_frame, values=["Light", "Dark"], width=120, state='readonly', border_width=1,
            command=self.change_theme_event)
        theme_box.set('Light')
        theme_box.grid(row=0, column=0, padx=(20, 0))
        ctk.CTkButton(
            settings_frame, text='', image=self.settings_img, width=22, fg_color="transparent",
            hover_color=("gray70", "gray30"), command=lambda: self.select_page(6)
        ).grid(row=0, column=1, padx=10)
        sidebar_frame.grid_rowconfigure(7, weight=1)
        settings_frame.grid_rowconfigure(0, weight=1)

    def select_page(self, index):
        if index == 0:
            self.at_cmd_button.configure(fg_color=('gray75', 'gray25'))
            self.at_cmd_frame.grid(row=0, column=1, sticky="nsew")
        else:
            self.at_cmd_button.configure(fg_color='transparent')
            self.at_cmd_frame.grid_forget()
        if index == 1:
            self.stress_test_button.configure(fg_color=('gray75', 'gray25'))
            self.stress_test_frame.grid(row=0, column=1, sticky="nsew")
        else:
            self.stress_test_button.configure(fg_color='transparent')
            self.stress_test_frame.grid_forget()
        if index == 2:
            self.log_offline_button.configure(fg_color=('gray75', 'gray25'))
            self.log_offline_frame.grid(row=0, column=1, sticky="nsew")
        else:
            self.log_offline_button.configure(fg_color='transparent')
            self.log_offline_frame.grid_forget()
        if index == 3:
            self.log_online_button.configure(fg_color=('gray75', 'gray25'))
            self.log_online_frame.grid(row=0, column=1, sticky="nsew")
        else:
            self.log_online_button.configure(fg_color='transparent')
            self.log_online_frame.grid_forget()
        if index == 4:
            self.bin_editor_button.configure(fg_color=('gray75', 'gray25'))
            self.bin_editor_frame.grid(row=0, column=1, sticky="nsew")
        else:
            self.bin_editor_button.configure(fg_color='transparent')
            self.bin_editor_frame.grid_forget()
        if index == 5:
            self.map_analyze_button.configure(fg_color=('gray75', 'gray25'))
            self.map_analyze_frame.grid(row=0, column=1, sticky="nsew")
        else:
            self.map_analyze_button.configure(fg_color='transparent')
            self.map_analyze_frame.grid_forget()
        if index == 6:
            self.settings_frame.grid(row=0, column=1, sticky="nsew")
        else:
            self.settings_frame.grid_forget()

    def change_theme_event(self, theme):
        if theme.lower() == ctk.get_appearance_mode().lower():
            return
        ctk.set_appearance_mode(theme)
        self.at_cmd_page.change_theme(theme)

    def show_messagebox(self, data):
        level, message, wait = data
        if level == 'info':
            box = CTkMessagebox(master=self, title="Info", message=message, icon="info", option_1="OK")
        elif level == 'error':
            box = CTkMessagebox(master=self, title="Error", message=message, icon="cancel", option_1="OK")
        elif level == 'ask':
            return CTkMessagebox(
                master=self, title="Ask?", message=message, icon="question", option_1="NO", option_2='YES'
            ).get() == 'YES'
        else:
            return
        if wait:
            return box.get()
        self.update()

    def show_release_note(self, data):
        def cancel():
            top.destroy()

        def upgrade():
            TopicBinder.notify(Topic.APP_UPGRADE_START, None)
            top.destroy()

        top = ctk.CTkToplevel(self)
        top.title('有新版本更新啦 ！')

        width, height = 500, 400
        textbox = TextBox(top, width=width, height=height)
        textbox.grid(row=0, column=0, columnspan=2, padx=50, pady=(20, 0), sticky='nsew')
        ctk.CTkButton(
            top, text='升级到最新版本', width=160, height=30, fg_color='orange', hover_color='tan', corner_radius=20,
            command=upgrade
        ).grid(row=1, column=0, padx=(50, 0), pady=30, sticky='w')
        ctk.CTkButton(
            top, text='取消升级', width=160, height=30, corner_radius=20, command=cancel
        ).grid(row=1, column=1, padx=(0, 50), pady=30, sticky='e')

        note_lines = ''
        try:
            for version, notes in data.items():
                if int(version) <= int(Profile.app_version):
                    continue
                note_lines += f"\n<ORANGE> ## > v{version}\n"
                for line in notes:
                    note_lines += f"<GREEN> : - {line}\n"
        except:
            pass
        Markdownx.render(textbox, note_lines)
        textbox.configure(state='disabled')
        x, y = int((self.winfo_screenwidth() - width) / 2), int((self.winfo_screenheight() - height - 200) / 2)
        top.resizable(False, False)
        top.geometry(f'+{x}+{y}')

    def settings_data_changed_callback(self, data):
        # 如果是viewmodel触发回调, 则是在子线程中进行，可能会对界面元素布局造成影响
        # 此处在customtkinter主线程(self.after)中进行
        theme = data.get('DEFAULT_THEME', 'Light')
        self.after(0, lambda: self.change_theme_event(theme))

    def show_messagebox_callback(self, data):
        self.after(0, lambda: self.show_messagebox(data))

    def show_release_note_callback(self, data):
        self.after(0, lambda: self.show_release_note(data))

    def binding(self):
        TopicBinder.bind(Topic.SETTINGS_DATA_CHANGED, self.settings_data_changed_callback)
        TopicBinder.bind(Topic.SHOW_MESSAGE_BOX, self.show_messagebox_callback)
        TopicBinder.bind(Topic.SHOW_RELEASE_NOTE, self.show_release_note_callback)
